// Xampler, a MIDI-driven sampler
// Copyright (C) 2006 Nick Thomas
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 2 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301 USA

#include "Engine.hpp"
#include "SynthMessage.hpp"
#include "State.hpp"
using namespace xampler;

int xampler::g_sample_rate;

Engine::Engine()
{
    m_jack_initialized = false;
    m_seq_initialized = false;

    m_first_free_voice = 0;
    for (int i = 0; i < MAX_POLYPHONY; i++) {
        m_voices[i].is_free = true;
        m_voices[i].free.next_free_voice = i + 1;
    }
    m_voices[MAX_POLYPHONY - 1].free.next_free_voice = -1;
}

bool Engine::init(Glib::ustring &error)
{
    return init_seq(error) && init_jack(error);
}

bool Engine::init_jack(Glib::ustring &error)
{
    jack_status_t jack_status;
    bool err = false;

    m_jack_client = jack_client_open("xampler", JackNullOption, &jack_status);

    if (jack_status) {
        error = "Could not connect to JACK.";
        err = true;
    }

    if (!err) {
        m_jack_port_out_1 =
            jack_port_register(m_jack_client, "out_1",
                               JACK_DEFAULT_AUDIO_TYPE,
                               JackPortIsOutput | JackPortIsTerminal, 0);
        m_jack_port_out_2 =
            jack_port_register(m_jack_client, "out_2",
                               JACK_DEFAULT_AUDIO_TYPE,
                               JackPortIsOutput | JackPortIsTerminal, 0);

        if (!(m_jack_port_out_1 && m_jack_port_out_2)) {
            error = "Could not create JACK output ports.";
            err = true;
            jack_client_close(m_jack_client);
        }
    }

    if (!err) {
        if (jack_set_process_callback(m_jack_client,
                                      &Engine::jack_processing_callback,
                                      this)) {
            error = "Could not register a JACK processing callback.";
            err = true;
        }
    }

    if (!err) {
        g_sample_rate = jack_get_sample_rate(m_jack_client);

        if (jack_activate(m_jack_client)) {
            error = "Could not activate JACK audio processing.";
            err = true;
        }
    }

    if (!err) {
        m_jack_initialized = true;
        return true;
    } else {
        return false;
    }
}

bool Engine::init_seq(Glib::ustring &error)
{
    bool err = false;
    int errnum = snd_seq_open(&m_seq, "default", SND_SEQ_OPEN_INPUT,
                              SND_SEQ_NONBLOCK);
    if (errnum < 0) {
        error = snd_strerror(errnum);
        err = true;
    }

    if (!err) {
        snd_seq_set_client_name(m_seq, "xampler");
        errnum = m_seq_id = snd_seq_client_id(m_seq);

        if (errnum < 0) {
            error = snd_strerror(errnum);
            snd_seq_close(m_seq);
            err = true;
        }
    }

    if (!err) {
        errnum = snd_seq_create_simple_port(m_seq, "xampler",
                                            SND_SEQ_PORT_CAP_WRITE |
                                            SND_SEQ_PORT_CAP_SUBS_WRITE,
                                            SND_SEQ_PORT_TYPE_MIDI_GENERIC |
                                            SND_SEQ_PORT_TYPE_SOFTWARE |
                                            SND_SEQ_PORT_TYPE_SYNTHESIZER);

        if (errnum >= 0) {
            m_seq_port = errnum;
        } else {
            error = snd_strerror(errnum);
            err = true;
        }
    }

    if (!err) {
        m_seq_initialized = true;
        return true;
    } else {
        return false;
    }
}

Engine::~Engine()
{
    if (m_jack_initialized) {
        jack_deactivate(m_jack_client);
        jack_client_close(m_jack_client);
    }

    if (m_seq_initialized) {
        snd_seq_delete_simple_port(m_seq, m_seq_port);
        snd_seq_close(m_seq);
    }
}

void Engine::allocate_voice(int instrument, int sample, int position,
                            float velocity)
{
    if (m_first_free_voice < 0) {
        // Exceeded max polyphony, don't do anything.
        return;
    }

    int voice = m_first_free_voice;
    m_first_free_voice = m_voices[voice].free.next_free_voice;

    m_voices[voice].is_free = false;
    m_voices[voice].used.instrument = instrument;
    m_voices[voice].used.sample = sample;
    m_voices[voice].used.position = position;
    m_voices[voice].used.velocity = velocity;

    SynthMessage msg;
    msg.type = SYNTH_MSG_SAMPLE_ON;
    msg.sample_on.instrument = instrument;
    msg.sample_on.sample = sample;
    g_synth_message_buffer->write(&msg);
}

void Engine::free_voice(int voice)
{
    SynthMessage msg;
    msg.type = SYNTH_MSG_SAMPLE_OFF;
    msg.sample_off.instrument = m_voices[voice].used.instrument;
    msg.sample_off.sample = m_voices[voice].used.sample;
    g_synth_message_buffer->write(&msg);

    m_voices[voice].is_free = true;
    m_voices[voice].free.next_free_voice = m_first_free_voice;
    m_first_free_voice = voice;
}

int Engine::jack_processing_callback(jack_nframes_t nframes, void *arg)
{
    return ((Engine *)arg)->jack_process(nframes);
}

int Engine::jack_process(jack_nframes_t nframes)
{
    float *out_1 = (float *)jack_port_get_buffer(m_jack_port_out_1, nframes);
    float *out_2 = (float *)jack_port_get_buffer(m_jack_port_out_2, nframes);

    seq_process();
    audio_process(out_1, out_2, nframes);

    return 0;
}

void Engine::seq_process()
{
    int errnum;
    snd_seq_event_t *event;

    while (1) {
        errnum = snd_seq_event_input(m_seq, &event);

        if (errnum < 0) {
            break;
        }

        switch (event->type) {
        case SND_SEQ_EVENT_NOTEON:
            if (event->data.note.velocity != 0) {
                allocate_voice(event->data.note.channel,
                               event->data.note.note,
                               -event_time_offset(event),
                               (float)event->data.note.velocity / 127); 
            }
            break;
        default:
            break;
        }

        snd_seq_free_event(event);
    }
}

int Engine::event_time_offset(snd_seq_event_t *event)
{
    // Stub; see issue #5.
    return 0;
}

void Engine::audio_process(float *out_1, float *out_2, jack_nframes_t nframes)
{
    for (int i = 0; i < (int)nframes; i++) {
        out_1[i] = out_2[i] = 0.0;
    }

    for (int i = 0; i < MAX_POLYPHONY; i++) {
        if (m_voices[i].is_free) {
            continue;
        }

        Sample *sample = g_state->get_instrument(m_voices[i].used.instrument).
            get_sample(m_voices[i].used.sample);

        if (!sample || sample->is_empty()) {
            free_voice(i);
            continue;
        }

        float left_coef, right_coef;
        left_coef = right_coef = sample->get_volume() *
            m_voices[i].used.velocity;
        if (sample->get_balance() < 0) {
            right_coef *= (1 + sample->get_balance());
        } else {
            left_coef *= (1 - sample->get_balance());
        }

        for (int j = 0;
             j < (int)nframes &&
                 m_voices[i].used.position < sample->get_num_frames();
             j++, m_voices[i].used.position++) {
            if (m_voices[i].used.position >= 0) {
                out_1[j] += left_coef *
                    sample->get_left_channel()[m_voices[i].used.position];
                out_2[j] += right_coef *
                    sample->get_right_channel()[m_voices[i].used.position];
            }
        }

        if (m_voices[i].used.position == sample->get_num_frames()) {
            free_voice(i);
        }
    }
}
